package org.broadway.jmx;

import java.io.IOException;
import java.util.Date;

import javax.management.AttributeChangeNotification;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationFilterSupport;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadway.interfaces.Context;
import org.broadway.interfaces.Controllable;
import org.broadway.monitor.MonitorConstants;
import org.broadway.monitor.ScriptedAction;

/**
 * This MBean is a JMX component that can be used to execute
 * scrited action.  The action can be a Groovy script, BeanShe, etc.
 * The component listens for events of type "broaday.action.trigger".
 * As an event Notification Listener, it fires the execute() method
 * of its associated ScriptedAction object.
 * @see Monitor
 * @see Scanner
 * @see JmxUtil
 * @see ScriptedAction
 * 
 * @author vladimir vivien
 *
 */
public class JmxScriptedAction implements BaseJmxBean, JmxScriptedActionMBean, Controllable, NotificationListener {

    private static final Log log = LogFactory.getLog(JmxScriptedAction.class);
    private MBeanServerConnection server;
    private ScriptedAction action;
    private boolean created;
    private Date dateCreated;
    private boolean started;
    private Date dateStarted;
    private Date dateStopped;
    private Date dateModified;
    private boolean destroyed;
    private Date lastExecDate;
    private long execCount = 0;

    /* ********************** Ctors *********************** */
    /***
     * Constructor for the Action component.  It takes an MBeanserver where it will be registered.
     * @param server
     */
    public JmxScriptedAction(MBeanServerConnection server) {
        super();
        setMBeanServer(server);
    }

    /**
     * Constructor that accepts a scripting file.
     * @param server - an instance of MBeanServer
     * @param scriptLocation - script file location
     */
    public JmxScriptedAction(MBeanServerConnection server, String scriptLocation) {
        this(server);
        setScriptFileLocation(scriptLocation);
    }

    /**
     * Constructor that takes server and action.
     * @param server
     * @param action
     */
    public JmxScriptedAction(MBeanServerConnection server, ScriptedAction action) {
        this(server);
        setScriptedAction(action);
    }

    /* **************** Getters / Setters ****************** */
    /**
     * Returns the script file location.
     * @return String
     */
    public String getScriptFileLocation() {
        return (action != null) ? action.getScriptFileLocation() : "UNKNOWN";
    }

    /**
     * Sets the location of script file.
     * @param location
     */
    public void setScriptFileLocation(String location) {
        getScriptedAction().setScriptFileLocation(location);
    }

    /**
     * Sets the Action object to use.
     * @param action
     */
    public void setScriptedAction(ScriptedAction action) {
        this.action = action;
    }

    /**
     * Returns the Action object used internally.
     * @return Action
     */
    public ScriptedAction getScriptedAction() {
        if (action == null) {
            action = new ScriptedAction();
        }
        return this.action;
    }

    /**
     * Setter for the MBeanServer used internally.
     */
    public void setMBeanServer(MBeanServerConnection server) {
        this.server = server;
    }

    /**
     * Getter returns the internal MBeanServer
     * @return MBeanServer
     */
    public MBeanServerConnection getMBeanServer() {
        if (server == null) {
            server = JmxUtil.getMBeanServer();
        }
        return this.server;
    }

    // ******************* Management Methods *************** //
    /**
     * This method is used to add a Broadway Jmx monitor to which
     * the action component will be listening to for event notification.
     * The name must be in the JMX ObjectName format.
     * @param jmxObjectName
     */
    public void addJmxMonitor(String jmxObjectName) {
        if (jmxObjectName == null) {
            return;
        }
        this.registerForMonitorNotifications(jmxObjectName);
        log.debug("Listening to [" + jmxObjectName + "] for event notifications");
    }

    /**
     * Removes an already-registered Broadway Jmx Monitor from the
     * Action's lists of event notifiers.
     * @see addJmxMonitor
     * @param jmxObjectName
     */
    public void removeJmxMonitor(String jmxObjectName) {
        if (jmxObjectName == null) {
            return;
        }
        this.unregisterForMonitorNotifications(jmxObjectName);
        log.debug("Removed [" + jmxObjectName + "]. No longer listening for notifications.");
    }

    /**
     * Runtime management method that returns Action's execution count.
     */
    public long getExecutionCount() {
        return execCount;
    }

    /**
     * Management method that returns the last execution date.
     */
    public String getLastExecutionDate() {
        return (lastExecDate != null) ? lastExecDate.toString() : "UNKNOWN";
    }

    /**
     * This is the implementation method for the NotificationListener.
     * It is executed when the action receives an event notification from 
     * an event notification broadcaster (see {@link JmxMonitor}).
     * @param notification - the notification object received.
     * @param handback - a general object passed by event generator.
     */
    public void handleNotification(Notification notification, Object handback) {
        if (!started) {
            return;
        }
        log.debug("Received notification [" + notification.getType() + "].");

        // handle attribute Notification changes
        if (AttributeChangeNotification.class.isAssignableFrom(notification.getClass())) {
            dateModified = new Date();
        }

        if (notification.getType().equals(JmxUtil.JmxConstants.BROADWAY_ACTION_SIGNAL.toString())) {
            execCount++;
            Context ctx = (Context) notification.getUserData();
            ctx.putValue("jmxNotification", notification);
            ctx.putValue("execCount", execCount);
            ctx.putValue("lastExecDate", new Date());

            Object result = this.getScriptedAction().execute(ctx);
            ctx.putValue(MonitorConstants.MonitorKeys.ACTION_RESULT, result);

            log.debug("JmxScriptedAction handled action notification OK.");
        }
    }

    // *************** Lifecycle Methods ******************* //
    /**
     * This lifecycle method should be called after setter values are applied and before start().
     * @throws JmxException
     */
    public void create() throws JmxException {
        if (created) {
            return;
        }
        if (action == null) {
            throw new JmxException("Unable to create component, no internal Action is found.");
        }
        created = true;
        dateCreated = new Date();
        log.info("JmxScriptedAction component created OK on " + dateCreated + ".");
    }

    /***
     * This method reports the status of the component's creation lifecycle.
     * @return boolean - true if it's created.
     */
    public boolean isCreated() {
        return created;
    }

    /**
     * Returns date the component was created in VM.
     * @return String
     */
    public String getDateCreated() {
        return (dateCreated != null) ? dateCreated.toString() : "UNKNOWN";
    }

    /***
     * Controller method to start the component.
     */
    @Override
    public void start() {
        if (started) {
            return;
        }
        if (!created) {
            create();
        }
        Date now = new Date();
        dateStarted = now;
        started = true;
        log.info("JmxScriptedAction component started OK on " + now.toString() + ".");
    }

    /***
     * Returns the status of the Action component.
     * @return
     */
    @Override
    public boolean isStarted() {
        return started;
    }

    /***
     * Getter to return the last date the component was starte.
     * @return String - a date value when system was started or UNKNOWN.
     */
    public String getDateStarted() {
        return (this.dateStarted != null) ? dateStarted.toString() : "UNKNOWN";
    }

    /***
     * Controller method to stop the component.
     */
    @Override
    public void stop() {
        if (!started) {
            return;
        }
        dateStopped = new Date();
        started = false;
        log.info("JmxScriptedAction component stopped OK on " + dateStopped + ".");
    }

    /***
     * Returns date when last stopped.
     * @return the date component was stopped or UKNOWN.
     */
    @Override
    public String getDateStopped() {
        return (this.dateStopped != null) ? dateStopped.toString() : "UNKNOWN";
    }

    /***
     * Lifecycle method.  Call this method invoke any clean up to be done before destroying method.
     */
    @Override
    public void destroy() {
        if (destroyed) {
            return;
        }
        if (started) {
            stop();
        }

        this.execCount = 0;
        this.dateCreated = null;
        this.dateModified = null;
        this.dateCreated = null;
        this.dateStarted = null;
        this.dateStopped = null;

        destroyed = true;

        log.info("JmxScriptedAction component destroyed OK.");
    }

    /***
     * Lifecycle - returns the status of the component's state.
     * @return true - if object has been destroyed.
     */
    public boolean isDestroyed() {
        return destroyed;
    }

    /**
     * Returns the date component was modified.
     */
    public String getDateModified() {
        return dateModified != null ? dateModified.toString() : "UNKNOWN";
    }

    /* ******************* Private Methods *********************/
    /**
     * Utility method that registers JmxScriptedAction  as a litener 
     * for JmxMonitor Action events.
     * @param jmxObjectName
     */
    private void registerForMonitorNotifications(String jmxObjectName) throws JmxException {
        ObjectName monitor = JmxUtil.createObjectName(jmxObjectName);
        try {
            if (this.getMBeanServer().isRegistered(monitor)) {
                NotificationFilterSupport filter = new NotificationFilterSupport();
                filter.enableType(JmxUtil.JmxConstants.BROADWAY_ACTION_SIGNAL.toString());
                JmxUtil.registerNotificationListener(this.getMBeanServer(), monitor,
                        this, filter, monitor);

                log.info("Registered Action MBean as a listener for component [" + jmxObjectName + "].");
            }
        } catch (IOException ex) {
            new JmxException("Unable to register for monitor notifications: ", ex);
        }
    }

    /**
     * Utility method to unregister this component from listening to monitors.
     * @param monitorName
     */
    private void unregisterForMonitorNotifications(String monitorName) throws JmxException{
        ObjectName monitor = JmxUtil.createObjectName(monitorName);
        try{
            if (this.getMBeanServer().isRegistered(monitor)) {
                JmxUtil.removeNotificationLitener(this.getMBeanServer(), monitor, this);
                log.info("Unregistered Action bean as listener for monitor [" + monitorName + "].");
            }
        }catch(IOException ex){
            new JmxException("Unable to unregister for monitor notifications: ", ex);
        }
    }
}
